package cn.ztuo.bitrade.util;

import cn.ztuo.bitrade.constant.SysConstant;
import cn.ztuo.bitrade.entity.ContractCoin;
import cn.ztuo.bitrade.entity.ContractOrderPositionType;
import cn.ztuo.bitrade.entity.ContractPosition;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 获取指数工具类
 */

@Slf4j
@Component
public class DataUtil {

    static class SymbolMap {
        private String symbol;
        private BigDecimal price;

        public void setSymbol(String symbol) {
            this.symbol = symbol;
        }

        public String getSymbol() {
            return this.symbol;
        }

        public void setPrice(BigDecimal price) {
            this.price = price;
        }

        public BigDecimal getPrice() {
            return this.price;
        }
    }

    @Autowired
    private RedisUtil redisUtilDouble;
    @Value("${index-source-file-path}")
    private String filePathDouble;

    private static RestTemplate restTemplate;
    private static RedisUtil redisUtil;
    private static String filePath;

    private static final int THREAD_NUM = 5;
    static ExecutorService pool = Executors.newCachedThreadPool();

    @PostConstruct
    public void init() {
        redisUtil = redisUtilDouble;
        filePath = filePathDouble;

    }

    //获取所有交易对 指数价格
    public static Map<String, BigDecimal> getIndexPrice() {
        CountDownLatch latch = new CountDownLatch(5);
        Map<String, BigDecimal> indexPriceMap = new HashMap<>();
        try {
            List<SymbolMap> symbolMaps = new ArrayList<>();
            RestTemplate restTemplate = new RestTemplate();
            Future<List<SymbolMap>> binanceList = pool.submit(new BinancePriceCallable(latch,restTemplate));
            Future<List<SymbolMap>> huobiList = pool.submit(new HuobiPriceCallable(latch,restTemplate));
            Future<List<SymbolMap>> OkexList = pool.submit(new OKEXPriceCallable(latch,restTemplate));
            Future<List<SymbolMap>> bitfinexList = pool.submit(new BitfinexPriceCallable(latch,restTemplate));
            Future<List<SymbolMap>> poloniexList = pool.submit(new PoloniexPriceCallable(latch,restTemplate));
            latch.await();
            symbolMaps.addAll(binanceList.get());
            symbolMaps.addAll(huobiList.get());
            symbolMaps.addAll(OkexList.get());
            symbolMaps.addAll(bitfinexList.get());
            symbolMaps.addAll(poloniexList.get());

            List<ContractCoin> coins = ContractUtils.findBaseSymbols();
            indexPriceMap = coins.stream().map(coin -> {
                List<SymbolMap> symbolMapList = symbolMaps.stream().filter(symbolMap -> symbolMap.getSymbol().equals(coin.getSymbol())).collect(Collectors.toList());
                BigDecimal indexPrice = symbolMapList.stream().map(a -> a.getPrice()).reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(symbolMapList.size()), 8, BigDecimal.ROUND_DOWN);
                coin.setIndexPrice(indexPrice);
                return coin;
            }).collect(Collectors.toMap(ContractCoin::getSymbol, ContractCoin::getIndexPrice));
            if (indexPriceMap != null && !indexPriceMap.isEmpty()) {
                Map indexPrice = indexPriceMap;
                redisUtil.hmset(SysConstant.CONTRACT_INDEX_PRICE, indexPrice);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return indexPriceMap;
    }

    //根据symbol获取当前指数价格
    public static BigDecimal getIndexPrice(String symbol) {
        Object indexPriceObj = redisUtil.hget(SysConstant.CONTRACT_INDEX_PRICE, symbol);
        if (indexPriceObj != null) {
            return new BigDecimal(indexPriceObj.toString());
        } else {
            Map<String, BigDecimal> indexPriceMap = getIndexPrice();
            return indexPriceMap.get(symbol);
        }
    }

    public static BigDecimal getBreakPrice(ContractPosition position) {
        ContractCoin coin = ContractUtils.getBaseSymbol(position.getSymbol());
        BigDecimal orderNum = new BigDecimal(position.getContractOccupyNum()).multiply(coin.getMinVolume());

        BigDecimal breakPrice = (orderNum.multiply(position.getPositionAvgPrice())
                .subtract(position.getHoldingBond()))
                .divide(orderNum.multiply((new BigDecimal("1")
                        .subtract(new BigDecimal("0.00075")).setScale(8))), 8, BigDecimal.ROUND_DOWN);
        return breakPrice;
    }

    public static BigDecimal getForcePrice(ContractPosition position) {
        ContractCoin coin = ContractUtils.getBaseSymbol(position.getSymbol());
        BigDecimal orderNum = new BigDecimal(position.getContractOccupyNum()).multiply(coin.getMinVolume());
        BigDecimal breakPrice = getBreakPrice(position);
        BigDecimal forcePrice = null;
        if (position.getPositionType() == ContractOrderPositionType.RISE) {
            forcePrice = (orderNum.multiply(position.getPositionAvgPrice())
                    .subtract(position.getHoldingBond())
                    .add(orderNum.multiply(new BigDecimal(0.00075)).multiply(breakPrice)))
                    .divide(orderNum.multiply(new BigDecimal(1).subtract(position.getMaintain())), 8, BigDecimal.ROUND_DOWN);
        }
        if (position.getPositionType() == ContractOrderPositionType.FALL) {
            forcePrice = position.getHoldingBond()
                    .add(orderNum.multiply(position.getPositionAvgPrice()))
                    .subtract(orderNum.multiply(new BigDecimal(0.00075)).multiply(breakPrice))
                    .divide(orderNum.multiply(new BigDecimal(1).add(position.getMaintain())), 8, BigDecimal.ROUND_DOWN);
        }

        return forcePrice;
    }


    static class BinancePriceCallable implements Callable<List<SymbolMap>> {
        //        CountDownLatch barrier;
        CountDownLatch barrier;
        RestTemplate restTemplate;

        public BinancePriceCallable(CountDownLatch barrier,RestTemplate restTemplate) {
            this.barrier = barrier;
            this.restTemplate = restTemplate;
        }

        @Override
        public List<SymbolMap> call() throws Exception {
            List<SymbolMap> list = DataUtil.getBinancePrice(restTemplate);
            barrier.countDown();
            return list;
        }
    }

    static class HuobiPriceCallable implements Callable<List<SymbolMap>> {
        CountDownLatch barrier;
        RestTemplate restTemplate;

        public HuobiPriceCallable(CountDownLatch barrier,RestTemplate restTemplate) {
            this.barrier = barrier;
            this.restTemplate = restTemplate;
        }

        @Override
        public List<SymbolMap> call() {
            List<SymbolMap> list = DataUtil.getHuobiPrice(restTemplate);
            barrier.countDown();
            return list;
        }
    }

    static class OKEXPriceCallable implements Callable<List<SymbolMap>> {
        CountDownLatch barrier;
        RestTemplate restTemplate;

        public OKEXPriceCallable(CountDownLatch barrier,RestTemplate restTemplate) {
            this.barrier = barrier;
            this.restTemplate = restTemplate;
        }

        @Override
        public List<SymbolMap> call() {
            List<SymbolMap> list = DataUtil.getOKEXPrice(restTemplate);
            barrier.countDown();
            return list;
        }
    }

    static class PoloniexPriceCallable implements Callable<List<SymbolMap>> {
        CountDownLatch barrier;
        RestTemplate restTemplate;

        public PoloniexPriceCallable(CountDownLatch barrier,RestTemplate restTemplate) {
            this.barrier = barrier;
            this.restTemplate = restTemplate;
        }

        @Override
        public List<SymbolMap> call() {
            List<SymbolMap> list = DataUtil.getPoloniexPrice(restTemplate);
            barrier.countDown();
            return list;
        }
    }

    static class BitfinexPriceCallable implements Callable<List<SymbolMap>> {
        CountDownLatch barrier;
        RestTemplate restTemplate;

        public BitfinexPriceCallable(CountDownLatch barrier,RestTemplate restTemplate) {
            this.barrier = barrier;
            this.restTemplate = restTemplate;
        }

        @Override
        public List<SymbolMap> call() {
            List<SymbolMap> list = DataUtil.getBitfinexPrice(restTemplate);
            barrier.countDown();
            return list;
        }
    }


    /**
     * 获得Binance 交易所BTCUSDT的行情
     *
     * @param restTemplate
     * @return
     */
    private static List<SymbolMap> getBinancePrice(RestTemplate restTemplate) {
        List<String> symbols = Arrays.asList(new String[]{"BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "BSVUSDT", "BNBUSDT"});
        List<SymbolMap> symbolMaps = new ArrayList<SymbolMap>();
        try {
            String url = "https://api.binance.com/api/v3/ticker/price";
            String forObject = HttpsUtil.doGet(url);
//            String forObject = readJsonFile(filePath + "binance.txt");
            List<JSONObject> objs = JSONArray.parseArray(forObject, JSONObject.class);
            objs.stream().filter(a -> symbols.contains(a.getString("symbol"))).forEach(a -> {
                SymbolMap symbolMap = new SymbolMap();
                symbolMap.setSymbol(a.getString("symbol").toUpperCase());
                symbolMap.setPrice(a.getBigDecimal("price").setScale(8, BigDecimal.ROUND_HALF_DOWN));
                symbolMaps.add(symbolMap);
            });
        } catch (Exception e) {
            log.error("获得Binance 交易所行情",e);
        } finally {
            return symbolMaps;
        }
    }

    /**
     * 获取 Huobi 交易所BTCUSDT的行情
     *
     * @param restTemplate
     * @return
     */
    private static List<SymbolMap> getHuobiPrice(RestTemplate restTemplate) {
        List<String> symbols = Arrays.asList(new String[]{"BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "BSVUSDT", "HTUSDT"});
        List<SymbolMap> symbolMaps = new ArrayList<SymbolMap>();
        try {
            String url = "https://api.huobi.pro/market/tickers";
            String s = HttpsUtil.doGet(url);
//            String s = readJsonFile(filePath + "huobi.txt");
            JSONObject jsonObject = JSONObject.parseObject(s);
            if (jsonObject.containsKey("status") && jsonObject.getString("status").equals("ok") && jsonObject.containsKey("data")) {
                List<JSONObject> objs = JSON.parseArray(jsonObject.getString("data"), JSONObject.class);
                objs.stream().filter(a -> symbols.contains(a.getString("symbol").toUpperCase())).forEach(a -> {
                    SymbolMap symbolMap = new SymbolMap();
                    symbolMap.setSymbol(a.getString("symbol").toUpperCase());
                    symbolMap.setPrice(a.getBigDecimal("close").setScale(8, BigDecimal.ROUND_HALF_DOWN));
                    symbolMaps.add(symbolMap);
                });
            }
        } catch (Exception e) {
            log.error("获取 Huobi 交易所的行情",e);
        } finally {
            return symbolMaps;
        }
    }

    /**
     * 获取 OKEX 交易所BTCUSDT的行情
     *
     * @param restTemplate
     * @return
     */
    private static List<SymbolMap> getOKEXPrice(RestTemplate restTemplate) {
        List<String> symbols = Arrays.asList(new String[]{"BTCUSDT", "ETHUSDT", "EOSUSDT", "LTCUSDT", "BSVUSDT", "OKBUSDT"});
        List<SymbolMap> symbolMaps = new ArrayList<SymbolMap>();
        try {
            String url = "https://aws.okex.com/api/spot/v3/instruments/ticker";
            String forObject = HttpsUtil.doGet(url);
//            String forObject = readJsonFile(filePath + "OKEX.txt");
            List<JSONObject> objs = JSONArray.parseArray(forObject, JSONObject.class);
            objs.stream().filter(a -> symbols.contains(a.getString("instrument_id").replaceAll("-", "").toUpperCase())).forEach(a -> {
                SymbolMap symbolMap = new SymbolMap();
                symbolMap.setSymbol(a.getString("instrument_id").replaceAll("-", "").toUpperCase());
                symbolMap.setPrice(a.getBigDecimal("last").setScale(8, BigDecimal.ROUND_HALF_DOWN));
                symbolMaps.add(symbolMap);
            });
        }catch (Exception e){
            log.error("获取 OKEX 交易所的行情",e);
        } finally {
            return symbolMaps;
        }
    }

    /**
     * 获取 Poloniex网 交易所的BTCUSDT 的行情
     *
     * @param restTemplate
     * @return
     */
    public static List<SymbolMap> getPoloniexPrice(RestTemplate restTemplate) {
        List<SymbolMap> symbolMaps = new ArrayList<>();
        List<String> symbols = Arrays.asList(new String[]{"BTCUSDT", "ETHUSDT", "BSVUSDT"});
        try {
            String url = "https://poloniex.com/public?command=returnTicker";
            String forObject = HttpsUtil.doGet(url);
//            String forObject = readJsonFile(filePath + "Poloniex.txt");
            JSONObject jsonObject = JSONObject.parseObject(forObject);
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                String symbol = (entry.getKey().split("_")[1] + entry.getKey().split("_")[0]).toUpperCase();
                if (symbols.contains(symbol)) {
                    SymbolMap symbolMap = new SymbolMap();
                    symbolMap.setSymbol(symbol);
                    JSONObject value = JSON.parseObject(entry.getValue().toString());
                    symbolMap.setPrice(value.getBigDecimal("last").setScale(8, BigDecimal.ROUND_DOWN));
                    symbolMaps.add(symbolMap);
                }
            }
        }catch (Exception e){
            log.error("获取 Poloniex网 交易所行情",e);
        } finally {
            return symbolMaps;
        }
    }


    /**
     * 获取 B网 交易所的BTCUSDT 的行情
     *
     * @param restTemplate
     * @return
     */
    private static List<SymbolMap> getBittrexPrice(RestTemplate restTemplate) {
        List<String> symbols = Arrays.asList(new String[]{"USD-BTC", "USD-ETH"});
        List<SymbolMap> symbolMaps = new ArrayList<SymbolMap>();
        try {
            String url = "https://api.bittrex.com/api/v1.1/public/getticker?market=";
            String forObject = HttpsUtil.doGet(url);
//            String forObject = readJsonFile(filePath + "Poloniex.txt");
            for (String symbol : symbols) {
                JSONObject json = JSON.parseObject(forObject);
                if (json.containsKey("success") && json.getBoolean("success")) {
                    List<JSONObject> objs = JSONArray.parseArray(json.getString("result"), JSONObject.class);
                    SymbolMap symbolMap = new SymbolMap();
                    symbolMap.setSymbol(symbol);
                    symbolMap.setPrice(objs.get(0).getBigDecimal("Bid").setScale(8, BigDecimal.ROUND_DOWN));
                    symbolMaps.add(symbolMap);
                }
            }
        }catch (Exception e){
            log.error("获取bittrex网 交易所行情",e);
        } finally {
            return symbolMaps;
        }
    }

    /**
     * 获取 B网 交易所的BTCUSDT 的行情
     *
     * @param restTemplate
     * @return
     */
    public static List<SymbolMap> getBitfinexPrice(RestTemplate restTemplate) {
        List<SymbolMap> symbolMaps = new ArrayList<SymbolMap>();
        List<String> symbols = Arrays.asList(new String[]{"BTCUSDT", "ETHUSDT"});
        try {
            String url = "https://api-pub.bitfinex.com/v2/tickers?symbols=ALL";
            String forObject = HttpsUtil.doGet(url);
//            String forObject = readJsonFile(filePath + "Bitfinex.txt");
            forObject = forObject.substring(2, forObject.length() - 3);
            String[] list = forObject.split("],\\[");
            List<String> list1 = Arrays.asList(list);
            list1.stream().filter(a -> symbols.contains(a.split(",")[0].substring(2, a.split(",")[0].length() - 1).toUpperCase() + "T")).forEach(a -> {
                SymbolMap symbolMap = new SymbolMap();
                symbolMap.setSymbol(a.split(",")[0].substring(2, a.split(",")[0].length() - 1).toUpperCase() + "T");
                symbolMap.setPrice(new BigDecimal(a.split(",")[1]).setScale(8, BigDecimal.ROUND_DOWN));
                symbolMaps.add(symbolMap);
            });
        }catch (Exception e){
            log.error("获取bitfinex网 交易所行情",e);
        } finally {
            return symbolMaps;
        }
    }

    public static String readJsonFile(String fileName) {
        String jsonStr = "";
        try {
            File jsonFile = new File(fileName);
            FileReader fileReader = new FileReader(jsonFile);
            Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");
            int ch = 0;
            StringBuffer sb = new StringBuffer();
            while ((ch = reader.read()) != -1) {
                sb.append((char) ch);
            }
            fileReader.close();
            reader.close();
            jsonStr = sb.toString();
            return jsonStr;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
